1 L
.KML
= L
.FeatureGroup
.extend({
6 initialize: function(kml
, options
) {
7 L
.Util
.setOptions(this, options
);
12 this.addKML(kml
, options
, this.options
.async
);
16 loadXML: function(url
, cb
, options
, async
) {
17 if (async
=== undefined) async
= this.options
.async
;
18 if (options
=== undefined) options
= this.options
;
20 var req
= new window
.XMLHttpRequest();
21 req
.open('GET', url
, async
);
23 req
.overrideMimeType('text/xml'); // unsupported by IE
25 req
.onreadystatechange = function() {
26 if (req
.readyState
!== 4) return;
27 if (req
.status
=== 200) cb(req
.responseXML
, options
);
32 addKML: function(url
, options
, async
) {
34 var cb = function(gpx
, options
) { _this
._addKML(gpx
, options
); };
35 this.loadXML(url
, cb
, options
, async
);
38 _addKML: function(xml
, options
) {
39 var layers
= L
.KML
.parseKML(xml
);
40 if (!layers
|| !layers
.length
) return;
41 for (var i
= 0; i
< layers
.length
; i
++) {
42 this.fire('addlayer', {
45 this.addLayer(layers
[i
]);
47 this.latLngs
= L
.KML
.getLatLngs(xml
);
54 L
.Util
.extend(L
.KML
, {
56 parseKML: function (xml
) {
57 var style
= this.parseStyle(xml
);
58 this.parseStyleMap(xml
, style
);
59 var el
= xml
.getElementsByTagName('Folder');
61 for (var i
= 0; i
< el
.length
; i
++) {
62 if (!this._check_folder(el
[i
])) { continue; }
63 l
= this.parseFolder(el
[i
], style
);
64 if (l
) { layers
.push(l
); }
66 el
= xml
.getElementsByTagName('Placemark');
67 for (var j
= 0; j
< el
.length
; j
++) {
68 if (!this._check_folder(el
[j
])) { continue; }
69 l
= this.parsePlacemark(el
[j
], xml
, style
);
70 if (l
) { layers
.push(l
); }
72 el
= xml
.getElementsByTagName('GroundOverlay');
73 for (var k
= 0; k
< el
.length
; k
++) {
74 l
= this.parseGroundOverlay(el
[k
]);
75 if (l
) { layers
.push(l
); }
80 // Return false if e's first parent Folder is not [folder]
81 // - returns true if no parent Folders
82 _check_folder: function (e
, folder
) {
84 while (e
&& e
.tagName
!== 'Folder')
88 return !e
|| e
=== folder
;
91 parseStyle: function (xml
) {
93 var sl
= xml
.getElementsByTagName('Style');
95 var attributes
= { color
: true, width
: true, Icon
: true, href
: true, hotSpot
: true };
97 function _parse(xml
) {
99 for (var i
= 0; i
< xml
.childNodes
.length
; i
++) {
100 var e
= xml
.childNodes
[i
];
102 if (!attributes
[key
]) { continue; }
103 if (key
=== 'hotSpot')
105 for (var j
= 0; j
< e
.attributes
.length
; j
++) {
106 options
[e
.attributes
[j
].name
] = e
.attributes
[j
].nodeValue
;
109 var value
= e
.childNodes
[0].nodeValue
;
110 if (key
=== 'color') {
111 options
.opacity
= parseInt(value
.substring(0, 2), 16) / 255.0;
112 options
.color
= '#' + value
.substring(6, 8) + value
.substring(4, 6) + value
.substring(2, 4);
113 } else if (key
=== 'width') {
114 options
.weight
= value
;
115 } else if (key
=== 'Icon') {
116 ioptions
= _parse(e
);
117 if (ioptions
.href
) { options
.href
= ioptions
.href
; }
118 } else if (key
=== 'href') {
119 options
.href
= value
;
126 for (var i
= 0; i
< sl
.length
; i
++) {
128 var options
= {}, poptions
= {}, ioptions
= {};
129 el
= e
.getElementsByTagName('LineStyle');
130 if (el
&& el
[0]) { options
= _parse(el
[0]); }
131 el
= e
.getElementsByTagName('PolyStyle');
132 if (el
&& el
[0]) { poptions
= _parse(el
[0]); }
133 if (poptions
.color
) { options
.fillColor
= poptions
.color
; }
134 if (poptions
.opacity
) { options
.fillOpacity
= poptions
.opacity
; }
135 el
= e
.getElementsByTagName('IconStyle');
136 if (el
&& el
[0]) { ioptions
= _parse(el
[0]); }
138 // save anchor info until the image is loaded
139 options
.icon
= new L
.KMLIcon({
140 iconUrl
: ioptions
.href
,
142 iconAnchorRef
: {x
: ioptions
.x
, y
: ioptions
.y
},
143 iconAnchorType
: {x
: ioptions
.xunits
, y
: ioptions
.yunits
}
146 style
['#' + e
.getAttribute('id')] = options
;
151 parseStyleMap: function (xml
, existingStyles
) {
152 var sl
= xml
.getElementsByTagName('StyleMap');
154 for (var i
= 0; i
< sl
.length
; i
++) {
156 var smKey
, smStyleUrl
;
158 el
= e
.getElementsByTagName('key');
159 if (el
&& el
[0]) { smKey
= el
[0].textContent
; }
160 el
= e
.getElementsByTagName('styleUrl');
161 if (el
&& el
[0]) { smStyleUrl
= el
[0].textContent
; }
163 if (smKey
=== 'normal')
165 existingStyles
['#' + e
.getAttribute('id')] = existingStyles
[smStyleUrl
];
172 parseFolder: function (xml
, style
) {
173 var el
, layers
= [], l
;
174 el
= xml
.getElementsByTagName('Folder');
175 for (var i
= 0; i
< el
.length
; i
++) {
176 if (!this._check_folder(el
[i
], xml
)) { continue; }
177 l
= this.parseFolder(el
[i
], style
);
178 if (l
) { layers
.push(l
); }
180 el
= xml
.getElementsByTagName('Placemark');
181 for (var j
= 0; j
< el
.length
; j
++) {
182 if (!this._check_folder(el
[j
], xml
)) { continue; }
183 l
= this.parsePlacemark(el
[j
], xml
, style
);
184 if (l
) { layers
.push(l
); }
186 el
= xml
.getElementsByTagName('GroundOverlay');
187 for (var k
= 0; k
< el
.length
; k
++) {
188 if (!this._check_folder(el
[k
], xml
)) { continue; }
189 l
= this.parseGroundOverlay(el
[k
]);
190 if (l
) { layers
.push(l
); }
192 if (!layers
.length
) { return; }
193 if (layers
.length
=== 1) { return layers
[0]; }
194 return new L
.FeatureGroup(layers
);
197 parsePlacemark: function (place
, xml
, style
) {
198 var h
, i
, j
, el
, options
= {};
200 var multi
= ['MultiGeometry', 'MultiTrack', 'gx:MultiTrack'];
202 el
= place
.getElementsByTagName(multi
[h
]);
203 for (i
= 0; i
< el
.length
; i
++) {
204 return this.parsePlacemark(el
[i
], xml
, style
);
208 el
= place
.getElementsByTagName('styleUrl');
209 for (i
= 0; i
< el
.length
; i
++) {
210 var url
= el
[i
].childNodes
[0].nodeValue
;
211 for (var a
in style
[url
]) {
212 options
[a
] = style
[url
][a
];
217 var parse
= ['LineString', 'Polygon', 'Point', 'Track', 'gx:Track'];
220 el
= place
.getElementsByTagName(tag
);
221 for (i
= 0; i
< el
.length
; i
++) {
222 var l
= this['parse' + tag
.replace(/gx:/, '')](el
[i
], xml
, options
);
223 if (l
) { layers
.push(l
); }
227 if (!layers
.length
) {
230 var layer
= layers
[0];
231 if (layers
.length
> 1) {
232 layer
= new L
.FeatureGroup(layers
);
235 var name
, descr
= '';
236 el
= place
.getElementsByTagName('name');
237 if (el
.length
&& el
[0].childNodes
.length
) {
238 name
= el
[0].childNodes
[0].nodeValue
;
240 el
= place
.getElementsByTagName('description');
241 for (i
= 0; i
< el
.length
; i
++) {
242 for (j
= 0; j
< el
[i
].childNodes
.length
; j
++) {
243 descr
= descr
+ el
[i
].childNodes
[j
].nodeValue
;
248 layer
.bindPopup('<h2>' + name
+ '</h2>' + descr
);
254 parseCoords: function (xml
) {
255 var el
= xml
.getElementsByTagName('coordinates');
256 return this._read_coords(el
[0]);
259 parseLineString: function (line
, xml
, options
) {
260 var coords
= this.parseCoords(line
);
261 if (!coords
.length
) { return; }
262 return new L
.Polyline(coords
, options
);
265 parseTrack: function (line
, xml
, options
) {
266 var el
= xml
.getElementsByTagName('gx:coord');
267 if (el
.length
=== 0) { el
= xml
.getElementsByTagName('coord'); }
269 for (var j
= 0; j
< el
.length
; j
++) {
270 coords
= coords
.concat(this._read_gxcoords(el
[j
]));
272 if (!coords
.length
) { return; }
273 return new L
.Polyline(coords
, options
);
276 parsePoint: function (line
, xml
, options
) {
277 var el
= line
.getElementsByTagName('coordinates');
281 var ll
= el
[0].childNodes
[0].nodeValue
.split(',');
282 return new L
.KMLMarker(new L
.LatLng(ll
[1], ll
[0]), options
);
285 parsePolygon: function (line
, xml
, options
) {
286 var el
, polys
= [], inner
= [], i
, coords
;
287 el
= line
.getElementsByTagName('outerBoundaryIs');
288 for (i
= 0; i
< el
.length
; i
++) {
289 coords
= this.parseCoords(el
[i
]);
294 el
= line
.getElementsByTagName('innerBoundaryIs');
295 for (i
= 0; i
< el
.length
; i
++) {
296 coords
= this.parseCoords(el
[i
]);
304 if (options
.fillColor
) {
307 if (polys
.length
=== 1) {
308 return new L
.Polygon(polys
.concat(inner
), options
);
310 return new L
.MultiPolygon(polys
, options
);
313 getLatLngs: function (xml
) {
314 var el
= xml
.getElementsByTagName('coordinates');
316 for (var j
= 0; j
< el
.length
; j
++) {
317 // text might span many childNodes
318 coords
= coords
.concat(this._read_coords(el
[j
]));
323 _read_coords: function (el
) {
324 var text
= '', coords
= [], i
;
325 for (i
= 0; i
< el
.childNodes
.length
; i
++) {
326 text
= text
+ el
.childNodes
[i
].nodeValue
;
328 text
= text
.split(/[\s\n]+/);
329 for (i
= 0; i
< text
.length
; i
++) {
330 var ll
= text
[i
].split(',');
334 coords
.push(new L
.LatLng(ll
[1], ll
[0]));
339 _read_gxcoords: function (el
) {
340 var text
= '', coords
= [];
341 text
= el
.firstChild
.nodeValue
.split(' ');
342 coords
.push(new L
.LatLng(text
[1], text
[0]));
346 parseGroundOverlay: function (xml
) {
347 var latlonbox
= xml
.getElementsByTagName('LatLonBox')[0];
348 var bounds
= new L
.LatLngBounds(
350 latlonbox
.getElementsByTagName('south')[0].childNodes
[0].nodeValue
,
351 latlonbox
.getElementsByTagName('west')[0].childNodes
[0].nodeValue
354 latlonbox
.getElementsByTagName('north')[0].childNodes
[0].nodeValue
,
355 latlonbox
.getElementsByTagName('east')[0].childNodes
[0].nodeValue
358 var attributes
= {Icon
: true, href
: true, color
: true};
359 function _parse(xml
) {
360 var options
= {}, ioptions
= {};
361 for (var i
= 0; i
< xml
.childNodes
.length
; i
++) {
362 var e
= xml
.childNodes
[i
];
364 if (!attributes
[key
]) { continue; }
365 var value
= e
.childNodes
[0].nodeValue
;
366 if (key
=== 'Icon') {
367 ioptions
= _parse(e
);
368 if (ioptions
.href
) { options
.href
= ioptions
.href
; }
369 } else if (key
=== 'href') {
370 options
.href
= value
;
371 } else if (key
=== 'color') {
372 options
.opacity
= parseInt(value
.substring(0, 2), 16) / 255.0;
373 options
.color
= '#' + value
.substring(6, 8) + value
.substring(4, 6) + value
.substring(2, 4);
379 options
= _parse(xml
);
380 if (latlonbox
.getElementsByTagName('rotation')[0] !== undefined) {
381 var rotation
= latlonbox
.getElementsByTagName('rotation')[0].childNodes
[0].nodeValue
;
382 options
.rotation
= parseFloat(rotation
);
384 return new L
.RotatedImageOverlay(options
.href
, bounds
, {opacity
: options
.opacity
, angle
: options
.rotation
});
389 L
.KMLIcon
= L
.Icon
.extend({
391 createIcon: function () {
392 var img
= this._createIcon('icon');
393 img
.onload = function () {
395 this.style
.width
= i
.width
+ 'px';
396 this.style
.height
= i
.height
+ 'px';
398 if (this.anchorType
.x
=== 'fraction' && this.anchorType
.y
=== 'fraction') {
399 img
.style
.marginLeft
= (-this.anchor
.x
* i
.width
) + 'px';
400 img
.style
.marginTop
= (-(1 - this.anchor
.y
) * i
.height
) + 'px';
402 if (this.anchorType
.x
=== 'pixels' && this.anchorType
.y
=== 'pixels') {
403 img
.style
.marginLeft
= (-this.anchor
.x
) + 'px';
404 img
.style
.marginTop
= (this.anchor
.y
- i
.height
+ 1) + 'px';
406 this.style
.display
= '';
411 _setIconStyles: function (img
, name
) {
412 L
.Icon
.prototype._setIconStyles
.apply(this, [img
, name
]);
413 // save anchor information to the image
414 img
.anchor
= this.options
.iconAnchorRef
;
415 img
.anchorType
= this.options
.iconAnchorType
;
420 L
.KMLMarker
= L
.Marker
.extend({
422 icon
: new L
.KMLIcon
.Default()
426 // Inspired by https://github.com/bbecquet/Leaflet.PolylineDecorator/tree/master/src
427 L
.RotatedImageOverlay
= L
.ImageOverlay
.extend({
431 _reset: function () {
432 L
.ImageOverlay
.prototype._reset
.call(this);
435 _animateZoom: function (e
) {
436 L
.ImageOverlay
.prototype._animateZoom
.call(this, e
);
439 _rotate: function () {
440 if (L
.DomUtil
.TRANSFORM
) {
441 // use the CSS transform rule if available
442 this._image
.style
[L
.DomUtil
.TRANSFORM
] += ' rotate(' + this.options
.angle
+ 'deg)';
443 } else if(L
.Browser
.ie
) {
444 // fallback for IE6, IE7, IE8
445 var rad
= this.options
.angle
* (Math
.PI
/ 180),
446 costheta
= Math
.cos(rad
),
447 sintheta
= Math
.sin(rad
);
448 this._image
.style
.filter
+= ' progid:DXImageTransform.Microsoft.Matrix(sizingMethod=\'auto expand\', M11=' +
449 costheta
+ ', M12=' + (-sintheta
) + ', M21=' + sintheta
+ ', M22=' + costheta
+ ')';
452 getBounds: function() {